#include "read.h"
/* vim:ts=4:sw=4:noet
 * (tabspace=4)
 * 
 * Copyright (C) 2004, 2005 Walter Doekes, <walter@djcvt.net>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "texts.h"
#ifdef _WIN32
#	define WINDOWS_LEAN_AND_MEAN
#	include <windows.h>
#	define O_LARGEFILE 0
#	define STDIN_FILENO _fileno(stdin)
#else /* !_WIN32 */
#	define _FILE_OFFSET_BITS 64
#	define _LARGEFILE64_SOURCE 1
#	include <unistd.h>
#	include <sys/types.h>
#	include <sys/stat.h>
#	include <errno.h>
#	include <sys/mman.h>
#	define O_BINARY 0
#endif /* !_WIN32 */
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <malloc.h>
#include <string.h>
#include <stdio.h>

struct readmmap_data {
#ifdef _WIN32
	HANDLE rs_fd;
	unsigned rs_size;
	unsigned rs_map_size;
	HANDLE rs_map;
	LPVOID rs_map_view;
	uint64_t rs_offset;
	uint64_t rs_size_left;
	uint64_t rs_total_size;
#else /* !_WIN32 */
	int rs_fd;
	unsigned rs_size;
	unsigned rs_map_size;
	void* rs_map;
	off_t rs_offset;
	off_t rs_size_left;
	off_t rs_total_size;
#endif /* !_WIN32 */
};

static int read_close(int fd) {
#ifdef _WIN32
	return close(fd);
#else /* !_WIN32 */
	int i, j;
	/* Try to close 10 times before giving up. */
	for(i = 0; (j = close(fd)) == -1 && errno == EINTR && i < 10; ++i)
		;
	return j;
#endif /* !_WIN32 */
}

int readall_fd(int fd, unsigned read_size, unsigned total_size, char** dest) {
	char* dest_p;
	size_t buf_size = (int)total_size + 1;
	size_t size_left = buf_size;
	*dest = (char*)malloc(buf_size * sizeof(char));
	if(*dest == NULL) {
#ifdef USE_SETERROR2
		set_error("malloc", -1);
#endif /* USE_SETERROR2 */
		return -1;
	}
	dest_p = *dest;
	if(read_size > INT_MAX) /* read(2) requires SSIZE_MAX as max */
		read_size = INT_MAX;
	while(1) {
		int ret = (int)read(fd, dest_p,
				read_size <= size_left ? read_size : size_left);
		if(ret == 0) {
			*dest_p = '\0';
			return dest_p - *dest;
		} else if(ret < 0) {
			*dest_p = '\0';
#ifdef USE_SETERROR2
			set_error("read", -1);
#endif /* USE_SETERROR2 */
			return -1;
		}
		dest_p += ret;
		size_left -= ret;
		if(size_left == 0) {
			*dest = realloc(*dest, buf_size * 2);
			if(*dest == NULL) {
#ifdef USE_SETERROR2
				set_error("realloc", -1);
#endif /* USE_SETERROR2 */
				return -1;
			}
			dest_p = *dest + buf_size;
			size_left = buf_size;
			buf_size *= 2;
		}
	}
}

int readall_file(const char* filename, char** dest) {
	int ret;
	unsigned read_size = 4096;
	unsigned total_size = 8196;
	int fd;
	struct stat st;
	/* The special filename NULL means stdin. */
	if(filename == NULL)
		return readall_fd(STDIN_FILENO, read_size, total_size, dest);
	if((fd = open(filename, O_RDONLY | O_BINARY)) == -1) {
#ifdef USE_SETERROR2
		set_error("open", -1);
#endif /* USE_SETERROR2 */
		return -1;
	}
	if(fstat(fd, &st) == 0) {
		total_size = st.st_size;
#ifndef _WIN32
		/* No block size easily available on windows. */
		read_size -= read_size % st.st_blksize;
		if(read_size == 0)
			read_size = st.st_blksize;
#endif /* _WIN32 */
	}
	ret = readall_fd(fd, read_size, total_size, dest);
	read_close(fd);
	return (int)ret;
}

int readmmap_open(const char* filename, struct readmmap_data** rsd) {
#ifdef _WIN32
	SYSTEM_INFO si;
	LARGE_INTEGER li;
	*rsd = (struct readmmap_data*)malloc(sizeof(struct readmmap_data));
	if(rsd == NULL) {
#ifdef USE_SETERROR2
		set_error("malloc", -1);
#endif /* USE_SETERROR2 */
		return -1;
	}
	GetSystemInfo(&si);
	/* 0x1000000 = 2^24 = 1.6MiB */
	(*rsd)->rs_map_size = 0x1000000 - (0x1000000 % si.dwAllocationGranularity);
	(*rsd)->rs_fd = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, 
					OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if((*rsd)->rs_fd != INVALID_HANDLE_VALUE) {
		if(GetFileSizeEx((*rsd)->rs_fd, &li) == TRUE) {
			(*rsd)->rs_total_size = (uint64_t)li.QuadPart;
			(*rsd)->rs_size_left = (*rsd)->rs_total_size;
			(*rsd)->rs_offset = 0;
			(*rsd)->rs_map_view = NULL;
			/* windows fails to map empty files :-/ */
			if((*rsd)->rs_total_size != 0) {
				(*rsd)->rs_map = CreateFileMapping((*rsd)->rs_fd, NULL,
												   PAGE_READONLY, 0, 0, NULL);
				if((*rsd)->rs_map != NULL)
					return 0;
#ifdef USE_SETERROR2
				else
					set_error("CreateFileMapping", -1);
#endif /* USE_SETERROR2 */
			} else {
				(*rsd)->rs_map = NULL;
				return 0;
			}
		} else {
#ifdef USE_SETERROR2
			set_error("GetFileSizeEx", -1);
#endif /* USE_SETERROR2 */
		}	
		CloseHandle((*rsd)->rs_fd);
	} else {
#ifdef USE_SETERROR2
		set_error("CreateFile", -1);
#endif /* USE_SETERROR2 */
	}
#else /* !_WIN32 */
	struct stat st;
	*rsd = (struct readmmap_data*)malloc(sizeof(struct readmmap_data));
	if(*rsd != NULL) {
		(*rsd)->rs_fd = open(filename, O_RDONLY | O_LARGEFILE);
		if((*rsd)->rs_fd != -1) {
			if(fstat((*rsd)->rs_fd, &st) != -1) {
				(*rsd)->rs_total_size = (*rsd)->rs_size_left = st.st_size;
				/* 0x1000000 = 2^24 = 1.6MiB */
				(*rsd)->rs_map_size = 0x1000000 - (0x1000000 % getpagesize());
				(*rsd)->rs_offset = 0;
				(*rsd)->rs_map = NULL;
				return 0;
			} else {
#ifdef USE_SETERROR2
				set_error("fstat", -1);
#endif /* USE_SETERROR2 */
			}
		} else {
#ifdef USE_SETERROR2
			set_error("open", -1);
#endif /* USE_SETERROR2 */
		}
		read_close((*rsd)->rs_fd);
	}
#endif /* !_WIN32 */
	free(*rsd);
	return -1;
}

int readmmap_getinfo(unsigned* blocksize, uint64_t* filesize,
		struct readmmap_data* rsd) {
	*blocksize = rsd->rs_map_size;
	*filesize = (uint64_t)rsd->rs_total_size;
	return 0;
}
	
int readmmap_get(const char** next, unsigned* size, struct readmmap_data* rsd) {
	if(rsd->rs_size_left == 0)
		return 0;
#ifdef _WIN32
	if(rsd->rs_map_view)
		UnmapViewOfFile(rsd->rs_map_view);
	rsd->rs_size = rsd->rs_size_left < rsd->rs_map_size ?
			rsd->rs_size_left : rsd->rs_map_size;
	rsd->rs_map_view = MapViewOfFile(rsd->rs_map, FILE_MAP_READ,
			rsd->rs_offset >> 32, (DWORD)rsd->rs_offset, rsd->rs_size);
	if(rsd->rs_map_view == NULL) {
#ifdef USE_SETERROR2
		set_error("MapViewOfFile", -1);
#endif /* USE_SETERROR2 */
		return -1;
	}
	*next = rsd->rs_map_view;
#else /* !_WIN32 */
	if(rsd->rs_map)
		munmap(rsd->rs_map, rsd->rs_size);
	rsd->rs_size = rsd->rs_size_left < rsd->rs_map_size ?
			rsd->rs_size_left : rsd->rs_map_size;
	rsd->rs_map = mmap(0, rsd->rs_size, PROT_READ, MAP_SHARED, rsd->rs_fd,
			rsd->rs_offset);
	if(rsd->rs_map == MAP_FAILED) {
#ifdef USE_SETERROR2
		set_error("mmap", -1);
#endif /* USE_SETERROR2 */
		rsd->rs_map = NULL;
		return -1;
	}
	madvise(rsd->rs_map, rsd->rs_size, MADV_SEQUENTIAL | MADV_WILLNEED);
	*next = rsd->rs_map;
#endif /* !_WIN32 */
	rsd->rs_offset += rsd->rs_size;
	rsd->rs_size_left -= rsd->rs_size;
	*size = rsd->rs_size;
	return 1;
}

int readmmap_close(struct readmmap_data** rsd) {
#ifdef _WIN32
	if((*rsd)->rs_map_view)
		UnmapViewOfFile((*rsd)->rs_map_view);
	if((*rsd)->rs_map)
		CloseHandle((*rsd)->rs_map);
	CloseHandle((*rsd)->rs_fd);
	free(*rsd);
	return 0;
#else /* !_WIN32 */
	if((*rsd)->rs_map)
		munmap((*rsd)->rs_map, (*rsd)->rs_size);
	read_close((*rsd)->rs_fd);
	free(*rsd);
	return 0;
#endif /* !_WIN32 */
}
